package me.w70.bot.gui.rs;

import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import me.w70.bot.Bot;

public class Mouse implements MouseMotionListener, MouseListener, Runnable {

	private final RSPanel client;

	private final MouseListener mouseDispatcher;

	private final MouseMotionListener mouseMotionDispatcher;

	private final Component source;

	public int botX, botY;

	public Mouse(RSPanel c, MouseListener oldMListener,
			MouseMotionListener oldMMListener) {
		this.client = c;
		this.mouseDispatcher = oldMListener;
		this.mouseMotionDispatcher = oldMMListener;
		this.source = c.loader.getComponentAt(1, 1);
	}

	public void dragMouse(final int x, final int y) {
		pressMouse(botX, botY, true);
		Bot.methods.wait(random(300, 500));
		MMouse(x, y);
		Bot.methods.wait(random(300, 500));
		releaseMouse(x, y, true);
	}

	public void mouseDragged(MouseEvent e) {
		if (Bot.allowInput) {
			botX = e.getX();
			botY = e.getY();
		}
	}

	public void mouseMoved(MouseEvent e) {
		if (Bot.allowInput) {
			botX = e.getX();
			botY = e.getY();

			moveMouse(botX, botY);
		}
	}

	public void mouseClicked(MouseEvent e) {
		if (Bot.allowInput) {
			validateMouseClick(e.getX(), e.getY(), e.getButton());
		}
	}

	public void mousePressed(MouseEvent e) {
		if (Bot.allowInput) {
			colour = Color.RED;
			pressMouse(e.getX(), e.getY(), e.getButton());
		}
	}

	public void mouseReleased(MouseEvent e) {
		if (Bot.allowInput) {
			colour = Color.GREEN;
			releaseMouse(e.getX(), e.getY(), e.getButton());
		}
	}

	public void mouseEntered(MouseEvent e) {

	}

	public void mouseExited(MouseEvent e) {

	}

	public void moveMouse(int x, int y) {
		botX = x;
		botY = y;
		mouseMotionDispatcher.mouseMoved(new MouseEvent(source,
				MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, x, y, 0,
				false));
	}

	public void pressMouse(int x, int y, int button) {
		botX = x;
		botY = y;
		mouseDispatcher.mousePressed(new MouseEvent(source,
				MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, x, y,
				1, false, button));
	}

	public void pressMouse(int x, int y, boolean button) {
		botX = x;
		botY = y;
		mouseDispatcher.mousePressed(new MouseEvent(source,
				MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, x, y,
				1, false, button ? MouseEvent.BUTTON1 : MouseEvent.BUTTON3));
	}

	public void releaseMouse(int x, int y, boolean button) {
		botX = x;
		botY = y;
		mouseDispatcher.mouseReleased(new MouseEvent(source,
				MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), 0, x, y,
				1, false, button ? MouseEvent.BUTTON1 : MouseEvent.BUTTON3));
	}

	public void releaseMouse(int x, int y, int button) {
		botX = x;
		botY = y;
		mouseDispatcher.mouseReleased(new MouseEvent(source,
				MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), 0, x, y,
				1, false, button));
	}

	public void validateMouseClick(int x, int y, int button) {
		botX = x;
		botY = y;
		mouseDispatcher.mouseClicked(new MouseEvent(source,
				MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, x, y,
				1, false, button));
	}

	public boolean clickMouse(int x, int y, int button) {
		try {
			pressMouse(x, y, button);
			Thread.sleep(random(100, 180));
			releaseMouse(x, y, button);
			validateMouseClick(x, y, button);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public boolean mouse(int x, int y, boolean button) {
		return clickMouse(x, y, (button ? MouseEvent.BUTTON1
				: MouseEvent.BUTTON3));
	}

	public boolean clickMouse(boolean left) {
		return clickMouse(botX, botY, (left ? MouseEvent.BUTTON1
				: MouseEvent.BUTTON3));
	}

	public int random(int min, int max) {
		return ((int) (Math.random() * (max - min))) + min;
		// return ((int) (((((System.nanoTime() >> 256) / 1000000000) / 10000))
		// * (max - min))) + min;
	}

	public final int msPerBit = random(50, 100);

	public final int reactionTime = random(50, 100);

	public boolean MMouse(int x, int y) {
		return MMouse(x, y, 5, 5);
	}

	public boolean MMouse(int x, int y, int randX, int randY) {
		if ((x == botX)) {
			if ((y == botY)) {
				return true;
			}
		}
		try {
			int mkx = botX;
			int mky = botY;
			Point[] controls = generateControls(botX, botY,
					random(x, x + randX), random(y, y + randY), 50, 120);
			Point[] spline = generateSpline(controls);
			long time = fittsLaw(Math.sqrt(Math.pow(mkx - botX, 2)
					+ Math.pow(mky - botY, 2)), 10);
			Point[] path = applyDynamism(spline, (int) time, 10);

			for (Point aPath : path) {
				botX = aPath.x;
				botY = aPath.y;
				moveMouse(aPath.x, aPath.y);
				Thread.sleep(random(75, 150));
			}
			return true;
		} catch (Exception e) {
			// DO NOTHING
		}
		return false;
	}

	public Point[] generateControls(int sx, int sy, int ex, int ey,
			int ctrlSpacing, int ctrlVariance) {
		double dist = Math.sqrt((sx - ex) * (sx - ex) + (sy - ey) * (sy - ey));
		double angle = Math.atan2(ey - sy, ex - sx);
		int ctrlPoints = (int) Math.floor(dist / ctrlSpacing);

		ctrlPoints = ctrlPoints * ctrlSpacing == dist ? ctrlPoints - 1
				: ctrlPoints;
		if (ctrlPoints <= 1) {
			ctrlPoints = 2;
			ctrlSpacing = (int) dist / 3;
			ctrlVariance = (int) dist / 2;
		}
		Point[] result = new Point[ctrlPoints + 2];

		result[0] = new Point(sx, sy);
		for (int i = 1; i < ctrlPoints + 1; i++) {
			double radius = ctrlSpacing * i;
			Point cur = new Point((int) (sx + radius * Math.cos(angle)),
					(int) (sy + radius * Math.sin(angle)));
			double percent = 1D - ((double) (i - 1) / (double) ctrlPoints);

			percent = percent > 0.5 ? percent - 0.5 : percent;
			percent += 0.25;
			int curVariance = (int) (ctrlVariance * percent);

			cur.x = random(cur.x, cur.x + curVariance * 2);
			cur.y = random(cur.y, cur.y + curVariance * 2);
			result[i] = cur;
		}
		result[ctrlPoints + 1] = new Point(ex, ey);
		return result;
	}

	public double fact(int n) {
		double result = 1;

		for (int i = 1; i <= n; i++) {
			result *= i;
		}
		return result;
	}

	public double nCk(int n, int k) {
		return fact(n) / (fact(k) * fact(n - k));
	}

	public void adaptiveMidpoints(java.util.Vector<Point> points) {
		int i = 0;
		while (i < points.size() - 1) {
			Point a = points.get(i++);
			Point b = points.get(i);

			if (Math.abs(a.x - b.x) > 1 || Math.abs(a.y - b.y) > 1) {
				if (Math.abs(a.x - b.x) != 0) {
					double slope = (double) (a.y - b.y) / (double) (a.x - b.x);
					double incpt = a.y - slope * a.x;

					for (int c = a.x < b.x ? a.x + 1 : b.x - 1; a.x < b.x ? c < b.x
							: c > a.x; c += a.x < b.x ? 1 : -1) {
						points.add(i++, new Point(c, (int) Math.round(incpt
								+ slope * c)));
					}
				} else {
					for (int c = a.y < b.y ? a.y + 1 : b.y - 1; a.y < b.y ? c < b.y
							: c > a.y; c += a.y < b.y ? 1 : -1) {
						points.add(i++, new Point(a.x, c));
					}
				}
			}
		}
	}

	public Point[] generateSpline(Point[] controls) {
		double degree = controls.length - 1;
		java.util.Vector<Point> spline = new java.util.Vector<Point>();
		boolean lastFlag = false;

		for (double theta = 0; theta <= 1; theta += 0.01) {
			double x = 0;
			double y = 0;

			for (double index = 0; index <= degree; index++) {
				double probPoly = nCk((int) degree, (int) index)
						* Math.pow(theta, index)
						* Math.pow(1D - theta, degree - index);

				x += probPoly * controls[(int) index].x;
				y += probPoly * controls[(int) index].y;
			}
			Point temp = new Point((int) x, (int) y);

			try {
				if (!temp.equals(spline.lastElement())) {
					spline.add(temp);
				}
			} catch (Exception e) {
				spline.add(temp);
			}
			lastFlag = theta != 1.0;
		}
		if (lastFlag) {
			spline.add(new Point(controls[(int) degree].x,
					controls[(int) degree].y));
		}
		adaptiveMidpoints(spline);
		return spline.toArray(new Point[0]);
	}

	public double gaussian(double t) {
		t = 10D * (t) - 5D;
		return (1D / (Math.sqrt(5D) * Math.sqrt(2D * Math.PI)))
				* Math.exp((-t * t) / 20D);
	}

	public double[] gaussTable(int steps) {
		double[] table = new double[steps];
		double step = 1D / (double) steps;
		double sum = 0;

		for (int i = 0; i < steps; i++) {
			sum += gaussian(i * step);
		}
		for (int i = 0; i < steps; i++) {
			table[i] = gaussian(i * step) / sum;
		}
		return table;
	}

	public Point[] applyDynamism(Point[] spline, int msForMove, int msPerMove) {
		int numPoints = spline.length;
		double msPerPoint = (double) msForMove / (double) numPoints;
		int undistStep = (int) Math.round((double) msPerMove / msPerPoint);
		int steps = (int) Math.floor(numPoints / undistStep);
		Point[] result = new Point[steps];
		double[] gaussValues = gaussTable(result.length);
		double currentPercent = 0;

		for (int i = 0; i < steps; i++) {
			currentPercent += gaussValues[i];
			int nextIndex = (int) Math.floor((double) numPoints
					* currentPercent);

			if (nextIndex < numPoints) {
				result[i] = spline[nextIndex];
			} else {
				result[i] = spline[numPoints - 1];
			}
		}
		if (currentPercent < 1D) {
			result[steps - 1] = spline[numPoints - 1];
		}
		return result;
	}

	public long fittsLaw(double targetDist, double targetSize) {
		return (long) (reactionTime + msPerBit
				* (Math.log10(targetDist / targetSize + 1) / Math.log10(2)));
	}

	public Color colour = Color.GREEN;

	public void run() {

	}

	private void ensureProperInput() {
		Component comp = Bot.methods.getClient().getCanvas();
		if (!comp.getMouseListeners()[0].equals(this)
				|| !comp.getKeyListeners()[0].equals(client.keyboardManager)) {
			System.out.println("Correcting input");
			for (MouseListener listener : comp.getMouseListeners())
				comp.removeMouseListener(listener);
			for (MouseMotionListener listener : comp.getMouseMotionListeners())
				comp.removeMouseMotionListener(listener);
			for (KeyListener listener : comp.getKeyListeners())
				comp.removeKeyListener(listener);
			comp.addMouseListener(this);
			comp.addMouseMotionListener(this);
			comp.addKeyListener(client.keyboardManager);
		}
	}
}
